<?php

Raise::load('core.dom.RaiseDomElement');

/**
 * RaiseDomNode class
 * Represents a DOM node
 *
 * @author Sam-Mauris Yong / hellclanner at live dot com
 * @license http://www.opensource.org/licenses/bsd-license New BSD License
 * @package phpraise.core.dom
 * @todo improve parsing with namespace handling
 * @since 1.2
 */
class RaiseDomNode extends RaiseDomElement {
    
    /**
     * The tag name
     * @var string
     */
    protected $tagName;
    
    /**
     * Get elements by its class
     * @param string $className The class name to identify the elements
     * @return RaiseCollection
     */
    public function getByClass($className) {
        return new RaiseCollection($this->searchByClass($this, $className));
    }

    /**
     * Search the entire DOM tree by class name
     * @param RaiseDomNode $node The node space to search in
     * @param string $className The class name to search for
     * @return array
     */
    private function searchByClass($node, $className){
        $space = array();
        if($node->children() != null){
            foreach($node->children() as $item){
                if($item instanceof RaiseDomElement){
                    $e = explode(' ', $item->attributes()->get('class'));
                    if(in_array($className, $e)){
                        $space[] = $item;
                    }
                    if($item->children()){
                        $childrenSpace = $this->searchByClass($item, $className);
                        if(count($childrenSpace) > 0){
                            $space = array_merge($space, $childrenSpace);
                        }
                    }
                }
            }
        }
        return $space;
    }

    /**
     * Get elements by CSS pseudo
     * @param string $pseudo The pseudo identifier
     * @return RaiseCollection
     */
    public function getByPseudo($pseudo) {
        return new RaiseCollection($this->searchByPseudo($this, $pseudo));
    }
    
    /**
     * Search the entire DOM tree by pseudo-code
     * @param RaiseDomNode $space The tree to search for
     * @param string $pseudo The pseudo-code
     * @return RaiseDomNode
     */
    private function searchByPseudo($space, $pseudo){
        $result = null;
        switch($pseudo){
            case 'first':
                if($space->children()){
                    $result = $space->children()->first();
                }
                break;
            case 'last':
                if($space->children()){
                    $result = $space->children()->last();
                }
                break;
        }
        return $result;
    }
    
    /**
     * Get elements by attribute and value combination
     * @param string $name The name of the attribute
     * @param string $value The attribute value
     * @return RaiseCollection
     */
    public function getByAttribute($name, $value) {
        return new RaiseCollection($this->searchByAttribute($this, $name, $value));
    }

    /**
     * Search the entire DOM tree by an attribute's value
     * @param RaiseDomNode $node The node space to search in
     * @param string $attribute The attribute name
     * @param string $value The attribute value
     * @return array
     */
    private function searchByAttribute($node, $attribute, $value){
        $space = array();
        if($node->children() != null){
            foreach($node->children() as $item){
                if($item instanceof RaiseDomElement){
                    if($item->attributes()->get($attribute) == $value){
                        $space[] = $item;
                    }
                    if($item->children()){
                        $childrenSpace = $this->searchByAttribute($item, $attribute, $value);
                        if(count($childrenSpace) > 0){
                            $space = array_merge($space, $childrenSpace);
                        }
                    }
                }
            }
        }
        return $space;
    }

    /**
     * Get elements by tag name
     * @param string $name The tag name to identify the elements
     * @return RaiseCollection
     */
    public function getByTagName($tagName) {
        return new RaiseCollection($this->searchByTagName($this, $tagName));
    }

    /**
     * Search the entire DOM tree by tag name
     * @param RaiseDomNode $node The node space to search in
     * @param string $tagName The tag name to search for
     * @return array
     */
    private function searchByTagName($node, $tagName){
        $space = array();
        if($node->children() != null){
            foreach($node->children() as $item){
                if($item instanceof RaiseDomElement){
                    if($item->tagName() == $tagName){
                        $space[] = $item;
                    }
                    if($item->children()){
                        $childrenSpace = $this->searchByTagName($item, $tagName);
                        if(count($childrenSpace) > 0){
                            $space = array_merge($space, $childrenSpace);
                        }
                    }
                }
            }
        }
        return $space;
    }

    /**
     * Get an element by its ID
     * @param string $id The ID to identify the element
     * @return RaiseDomNode
     */
    public function getById($id) {
        return $this->searchById($this, $id);
    }

    /**
     * Search the entire DOM Tree for an element using the element ID
     * @param RaiseDomNode $node The node space to search in
     * @param string $id The unique ID
     * @return RaiseDomNode
     */
    private function searchById($node, $id){
        $result = null;
        if($node->children()){
            foreach($node->children() as $item){
                if($item instanceof RaiseDomElement){
                    if($item->attributes()->get('id') == $id){
                        $result = $item;
                    }
                    if(!$result && $item->children()){
                        $result = $this->searchById($item, $id);
                    }
                }
                if($result){
                    break;
                }
            }
        }
        return $result;
    }

    /**
     * Get elements using CSS selectors
     * @param string $selector The CSS selector
     * @return RaiseCollection
     */
    public function select($selectors){
        $selectors = trim($selectors);
        
        if(strpos($selectors, ',') !== false){
            $aSelectors = explode(',', $selectors);
            foreach($aSelectors as $selector){
                $this->select($selector);
            }
        }else{
            if(!$selectors){
                throw new RaiseInvalidArgumentException('CSS Selector cannot be empty');
            }

            $space = $this;

            $selLen = strlen($selectors);
            $parsePos = 0;

            $lastKeyChar = false;
            
            for($startParsePos = $parsePos; $parsePos < $selLen + 1; $parsePos++){
                if($parsePos < $selLen){
                    $c = substr($selectors, $parsePos, 1);
                }else{
                    $c = false;
                }
                if(!$c || in_array($c, array('#','.',':', ' '))){
                    if($lastKeyChar == false){
                        $tag = substr($selectors, $startParsePos, $parsePos - $startParsePos);
                        if($tag){
                            $x = new RaiseDomNode('root');
                            $x->children = new RaiseCollection($this->searchByTagName($space, $tag));
                            $space = $x;
                        }
                    }elseif($lastKeyChar == '#'){
                        $id = substr($selectors, $startParsePos, $parsePos - $startParsePos);
                        $space = $this->searchById($space, $id);
                    }elseif($lastKeyChar == '.'){
                        $class = substr($selectors, $startParsePos, $parsePos - $startParsePos);
                        $x = new RaiseDomNode('root');
                        $x->children = new RaiseCollection($this->searchByClass($space, $class));
                        $space = $x;
                    }elseif($lastKeyChar == ':'){
                        $pseudo = substr($selectors, $startParsePos, $parsePos - $startParsePos);
                        $space = $this->searchByPseudo($space, $pseudo);
                    }
                    if($space == null){
                        return null;
                    }
                    if(!$c){
                        break;
                    }
                    $startParsePos = $parsePos + 1;
                    $lastKeyChar = $c;
                }
                if($c == ' '){
                    $lastKeyChar = false;
                }
            }

        }
        return $space;
    }
    
}